package com.ruoyi.common.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.github.wxpay.sdk.WXPayUtil.generateNonceStr;

@Component
public class WechatJsSdkUtil {

    private static StringRedisTemplate stringRedisTemplate;

    @Resource
    public void setStaticBean(StringRedisTemplate bean) { // ✅ 通过setter注入
        WechatJsSdkUtil.stringRedisTemplate = bean;
    }

    private static final String CACHE_KEY = "wechat:jsapi_ticket";
    private static final String LOCK_KEY = "wechat:lock:jsapi_ticket";
    private static final int EXPIRE_SECONDS = 7000; // 比7200少200秒，避免临界过期
    /**
     * 生成签名参数包
     * @param url 前端传入的当前页面URL（不含#部分）
     */
    public static Map<String, Object> generateConfig(String appId, String secret, String url) {
        if (url.indexOf("#")>0) {
            throw new RuntimeException("前端传入的当前页面URL（不含#部分）不能为空");
        }
        // 1. 获取access_token（带缓存）
        String accessToken = getCachedAccessToken(appId, secret);

        // 2. 获取jsapi_ticket（带缓存）
        String jsapiTicket = getCachedJsapiTicket(appId, accessToken);

        // 3. 生成签名参数
        long timestamp = System.currentTimeMillis() / 1000;
        String nonceStr = generateNonceStr();
        String signature = generateSignature(jsapiTicket, nonceStr, timestamp, url);

        return Map.of(
            "appId", appId,
            "timestamp", timestamp,
            "nonceStr", nonceStr,
            "signature", signature
        );
    }

    /**
     * 获取带缓存的jsapi_ticket
     * @param accessToken 有效的微信access_token
     */
    public static String getCachedJsapiTicket(String appId, String accessToken) {
        // 1. 优先从缓存读取
        String ticket = stringRedisTemplate.opsForValue().get(CACHE_KEY);
        if (ticket != null){
            if (stringRedisTemplate.hasKey(ticket) && isTicketValid()) {
                return ticket;
            }
        }


        // 2. 加分布式锁（防止并发刷新）
        String lockValue = UUID.randomUUID().toString();
        try {
            Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(
                    LOCK_KEY, lockValue, 10, TimeUnit.SECONDS
            );

            if (Boolean.TRUE.equals(locked)) {
                // 3. 调用微信接口
                String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"
                        + "?access_token=" + accessToken + "&type=jsapi";
                RestTemplate restTemplate = new RestTemplate();
                JSONObject result = restTemplate.getForObject(url, JSONObject.class);

                if (result != null && result.getIntValue("errcode") == 0) {
                    ticket = result.getString("ticket");
                    // 4. 更新缓存（提前200秒过期）
                    stringRedisTemplate.opsForValue().set(
                            CACHE_KEY, ticket, EXPIRE_SECONDS, TimeUnit.SECONDS
                    );
                }
            } else {
                // 等待其他实例完成刷新（最多等待2秒）
                Thread.sleep(2000);
                return stringRedisTemplate.opsForValue().get(CACHE_KEY);
            }
        } catch (Exception e) {
            throw new RuntimeException("获取jsapi_ticket失败: " + e.getMessage());
        } finally {
            // 原子性释放锁
            if (lockValue.equals(stringRedisTemplate.opsForValue().get(LOCK_KEY))) {
                stringRedisTemplate.delete(LOCK_KEY);
            }
        }
        return ticket;
    }

    // 检查缓存有效性（剩余时间>300秒）
    private static boolean isTicketValid() {
        Long ttl = stringRedisTemplate.getExpire(CACHE_KEY, TimeUnit.SECONDS);
        return ttl != null && ttl > 300;
    }

    public static String getCachedAccessToken(String appId, String secret) {
        String cacheKey = "wechat:access_token:" + appId;
        String accessToken = stringRedisTemplate.opsForValue().get(cacheKey);

        // 1. 缓存命中且有效
        if ((accessToken != null && stringRedisTemplate.hasKey(cacheKey))) {
            return accessToken;
        }

        // 2. 分布式锁防止并发刷新
        String lockKey = "wechat:lock:access_token:" + appId;
        String lockValue = UUID.randomUUID().toString().replace("-","");
        try {
            // 尝试获取锁（设置 10 秒超时）
            Boolean locked = stringRedisTemplate.opsForValue().setIfAbsent(
                    lockKey, lockValue, 10, TimeUnit.SECONDS
            );

            if (locked != null && locked) {
                // 3. 调用微信接口获取新 Token
                String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + secret;
                RestTemplate restTemplate = new RestTemplate();
                JSONObject result = restTemplate.getForObject(url, JSONObject.class);

                if (result != null && result.containsKey("access_token")) {
                    accessToken = result.getString("access_token");
                    int expiresIn = result.getIntValue("expires_in");

                    // 4. 缓存新 Token（提前 120 秒过期）
                    stringRedisTemplate.opsForValue().set(
                            cacheKey, accessToken, expiresIn - 120, TimeUnit.SECONDS
                    );
                }
            } else {
                // 等待其他线程刷新完成（最多等待 2 秒）
                Thread.sleep(2000);
                return stringRedisTemplate.opsForValue().get(cacheKey);
            }
        } catch (Exception e) {
            throw new RuntimeException("获取 AccessToken 失败: " + e.getMessage());
        } finally {
            // 释放锁（需原子操作）
            if (lockValue.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return accessToken;
    }

    // SHA1签名方法
    private static String generateSignature(String ticket, String nonceStr, long timestamp, String url) {
        String paramString = StrUtil.format("jsapi_ticket={}&noncestr={}&timestamp={}&url={}", ticket, nonceStr, timestamp, url);

        return SecureUtil.sha1(paramString);
    }
}
